<?php

/*
 * Author: Victor Salamon <salamonv@yahoo.com>
 * Copyright 2007 Victor Salamon
 * License: LGPL (see COPYING.LESSER)
 * Version: 1.0
 * http://code.google.com/p/yuiolait/
 * http://sourceforge.net/projects/phpolait
 *
 * Phpolait is Copyright Craig Mason-Jones
 * Phpolait Link  http://www.sourceforge.net/projects/phpolait
 * Phpolait Author Craig Mason-Jones (craig@lateral.co.za)
 *
 * Requirements:
 *     yui library (parts)
 *     Services_JSON (for php4)
 *     PHP versions 4 and 5 - tested
 *     Browsers: (Firefox 2.x) (IE 5.5, 6.5, 7) (Opera 9) - tested
 */

/* ------------------------ CONFIGURATION START ------------------------ */
/* The path to your copy of the yui library. */
define('YUIOLAIT_YUI_PATH', "../yui/");

/* Define to True to turn debugging on, or False for debugging off. */
define('YUIOLAIT_DEBUG', False);
/* ------------------------ CONFIGURATION END -------------------------- */

require_once("../utl/json-provide.php");

if( YUIOLAIT_DEBUG ) {
    require_once("../utl/utl-logit.php");
} else {
    function php_logit($msg) {}
}

class JSONRpcServer
{
    var $yols_object;

    function JSONRpcServer($object) 
    {
        $input = file_get_contents("php://input");
        php_logit("JSONRpcServer:input=$input");
        $request = json_decode($input);

        /* If we have no request object, we are processing our page, 
         * so prepare the js Wrappers. This code is utilized when 
         * the page is executed through - say - an initial GET request. */
        if ($request==null) {
            $this->yols_object = $object;
            php_logit("JSONRpcServer:null request");
            return;
        }

        /* Ok, it's the AJAX request we're waiting for. */
        $reply = array (
            // TODO: is this for multiple outstanding requests ? 
            // or just for debugging ? 
            // "yui_rep_id" => $request->yui_req_id,

            "yui_rep_result" => null,
            "yui_rep_error" => null
        );

        /* We've got the incoming JSON request object in request - we need 
         * to identify the method and the parameters */
        $method = $request->yui_req_method;
        php_logit("JSONRpcServer:method=$method");

        if (is_object($object)) {
            php_logit("JSONRpcServer:object is object");

            if (!method_exists($object, $method)) {
                php_logit("JSONRpcServer:method does not exist");

                $reply['yui_rep_error'] = 
                    "No such method (" . $method . ") exists on this server.";
            } else {
                php_logit("JSONRpcServer:method does exist");

                /* TODO: Try to catch an error in the call: use 
                 * set_error_handler and restore_error_handler...? */
                $res = call_user_func_array(array(&$object, 
                    $method), $request->yui_req_args);
                if( FALSE == $res ) {
                    php_logit("JSONRpcServer:call_user_func_array error $errstr ($errno)" );
                }
                php_logit("JSONRpcServer:finished calling" );

                $reply['yui_rep_result'] = $res;
            }
        } else {
            php_logit("JSONRpcServer:object is not object");

            if (!function_exists($method)) {
                $reply['yui_rep_error'] = 
                    "No such function (" . $method . ") exists on this server.";
            } else {
                $reply['yui_rep_result'] = call_user_func_array($method, 
                    $request->yui_req_args);
            }
        }
        $jsonreply = json_encode($reply);
        php_logit("JSONRpcServer:jsonreply=$jsonreply");
        print($jsonreply);

        /* Vick: This is here to avoid processing the rest of the
         * script in case we get the AJAX request. Looking up the client 
         * class/object methods is all possible because this constructor
         * gets executed, but not the rest of the code. */
        exit(0);
    }

    function jsWrapperHeader($pageUrl, $methodArray) 
    {
        if( YUIOLAIT_DEBUG ) {
            $dbgJs = "<script type='text/javascript' src='../utl/utl-debug.js'></script>";
        } else {
            $dbgJs = "<script type='text/javascript'>function jslog(m) {}</script>";
        }

        $yp = YUIOLAIT_YUI_PATH;
        $header = <<<EOJS
$dbgJs
<script type="text/javascript" src="$yp/build/yahoo/yahoo-min.js"></script>  
<script type="text/javascript" src="$yp/build/json/json-beta-min.js"></script>  
<script type="text/javascript" src="$yp/build/event/event-min.js"></script>
<script type="text/javascript" src="$yp/build/connection/connection-min.js"></script>
<script language="javascript">
function arrayToAssociative(arr, len)
{
    var assoc = {};
    var i;
    for( i = 0; i < len; i++ ) {
        assoc[i] = arr[i];
    }
    return assoc;
}

/* Returns a closure. */
function getYUIConnectClosure(methodName, serviceURL)
{
    return (function() {
        a = arguments;
        jslog("step0:a.length=" + a.length);
        jslog("step0:a=" + a);
        /* "arguments" is an Array but JSON.stringify only supports {} */

        for (var i = 0; i < arguments.length; i++) {
            jslog("step0:a[" + i + "]=" + a[i]);
        }

        var req = {};
        var assocArgs = arrayToAssociative(a, a.length-1);
        jslog("step2");
        req["yui_req_args"] = assocArgs;
        req["yui_req_method"] = methodName;
        jslog("step4:req=" + req);
        var jsonReq = YAHOO.lang.JSON.stringify(req);
        jslog("step5:jsonReq=" + jsonReq);
        jslog("step6:url=" + serviceURL);

        var cb = {
            success: function(o) { 
                jslog('cb:succeeded:JSON.parse('+o.responseText+')');
                var jscr = YAHOO.lang.JSON.parse(o.responseText);
                jslog('cb:succeeded:'+jscr['yui_rep_result']); 
                var usrCb = o.argument;
                usrCb(jscr['yui_rep_result'], jscr['yui_rep_error']);
            },
            failure: function(o) { 
                var err = o.status + '/' + o.statusText;
                jslog('cb:failed:' + err); 
                var usrCb = o.argument;
                usrCb(null, err);
            },
            argument: a[a.length-1]
        }
        var conn = YAHOO.util.Connect.asyncRequest('POST', serviceURL, 
            cb, jsonReq); 
        jslog("conn=" + conn);
    });
}

function YUIOLait() 
{
    var serviceURL = "$pageUrl";

EOJS;

    /* Continuing php statements in jsWrapperHeader: */
    $header = str_replace("%PAGE_NAME%", $pageUrl, $header);
    $methodList = "'" . implode($methodArray, "','") . "'";
    $header = str_replace("%METHODLIST%", $methodList, $header);
    print $header;
} /* end of jsWrapperHeader */

    function jsWrapperMethod($method)
    {
        /* The last argument contains the callback function reference.
         * For the posting to the server, we replace it with the 
         * target method name. */
        print <<< EOJS
        this.$method = getYUIConnectClosure("$method", serviceURL);

EOJS;
    }

    function jsWrapperFooter($varName) 
    {
        print " }
var $varName = new YUIOLait();
</script>";
    }

    function javascript($proxyvar) 
    {
        $methods = get_class_methods( $this->yols_object );

        $this->jsWrapperHeader($_SERVER["PHP_SELF"], $methods);
        foreach ($methods as $name) {
            $this->jsWrapperMethod($name);
        }
        $this->jsWrapperFooter($proxyvar);
    }
}

php_logit("end of script");
